﻿using Cronos;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Xms.Abp.Threading;

public class Cron : IEquatable<Cron>
{
    private CronExpression _CronExpression;
    private string _Expression;
    private (DateTime StartTime, DateTime EndTime)? _Years;
    private DateTimeKind _DateTimeKind;
    public static Cron Parse(string expression, DateTimeKind kind = DateTimeKind.Unspecified)
    {
        return Parse(expression, CronFormat.IncludeSeconds, kind);
    }
    public static Cron Parse(string expression, CronFormat format, DateTimeKind kind = DateTimeKind.Unspecified)
    {
        var sp = expression.Split(' ');
        var exp = expression;
        (DateTime StartTime, DateTime EndTime)? _years = null;
        if (sp.Length == (format == CronFormat.IncludeSeconds ? 7 : 6))
        {
            exp = sp.Take((format == CronFormat.IncludeSeconds ? 6 : 5)).JoinAsString(" ");
            var ysp = sp.Last().Split('-');
            var start = new DateTime(int.Parse(ysp.First()), 1, 1, 0, 0, 0, kind);
            var end = new DateTime(int.Parse(ysp.Last()) + 1, 1, 1, 0, 0, 0, kind).AddTicks(-1);
            _years = (start, end);
        }
        var cron = new Cron
        {
            _CronExpression = CronExpression.Parse(exp, format),
            _Expression = expression,
            _Years = _years,
            _DateTimeKind = kind,
        };
        return cron;
    }

    private Cron()
    {

    }

    private DateTime ConvertToUTC(DateTime dateTime)
    {
        return dateTime.ChangeKind(_DateTimeKind).AsKind(DateTimeKind.Utc);
    }

    private DateTime ConvertToResult(DateTime dateTime, DateTimeKind resultKind)
    {
        return dateTime.AsKind(_DateTimeKind).ChangeKind(resultKind);
    }

    public bool Equals(Cron other)
    {
        return other == this;
    }

    public override bool Equals(object obj)
    {
        return obj is Cron cron && cron == this;
    }

    public override int GetHashCode()
    {
        return _Expression.GetHashCode();
    }

    public DateTime? GetNextOccurrence(DateTime from, bool inclusive = false)
    {
        var fromUtc = ConvertToUTC(from);
        if (_Years.HasValue && _Years.Value.StartTime <= fromUtc && fromUtc <= _Years.Value.EndTime || !_Years.HasValue)
        {
            var result = _CronExpression.GetNextOccurrence(fromUtc, inclusive);
            if (result.HasValue)
            {
                result = result.Value.AsKind(_DateTimeKind);
                if (_Years.HasValue && result > _Years.Value.EndTime)
                {
                    return null;
                }
                result = result.Value.ChangeKind(from.Kind);
            }
            return result;
        }
        return null;
    }

    public DateTime? GetNextOccurrence(DateTime from, TimeZoneInfo zone, bool inclusive = false)
    {
        var fromUtc = ConvertToUTC(from);
        if (_Years.HasValue && _Years.Value.StartTime <= fromUtc && fromUtc <= _Years.Value.EndTime || !_Years.HasValue)
        {
            var result = _CronExpression.GetNextOccurrence(fromUtc, zone, inclusive);
            if (result.HasValue)
            {
                result = result.Value.AsKind(_DateTimeKind);
                if (_Years.HasValue && result > _Years.Value.EndTime)
                {
                    return null;
                }
                result = result.Value.ChangeKind(from.Kind);
            }
            return result;
        }
        return null;
    }

    public DateTimeOffset? GetNextOccurrence(DateTimeOffset from, TimeZoneInfo zone, bool inclusive = false)
    {
        var fromUtc = from.UtcDateTime;
        if (_Years.HasValue && _Years.Value.StartTime <= fromUtc && fromUtc <= _Years.Value.EndTime || !_Years.HasValue)
        {
            var result = _CronExpression.GetNextOccurrence(from, zone, inclusive);
            if (result.HasValue && _Years.HasValue)
            {
                var t = result.Value.UtcDateTime.AsKind(_DateTimeKind);
                if (t > _Years.Value.EndTime)
                {
                    return null;
                }
            }
            return result;
        }
        return null;
    }

    public IEnumerable<DateTime> GetOccurrences(DateTime from, DateTime to, bool fromInclusive = true, bool toInclusive = false)
    {
        var fromUtc = ConvertToUTC(from);
        var toUtc = ConvertToUTC(to);
        if (fromUtc > toUtc) return Enumerable.Empty<DateTime>();
        if (_Years.HasValue && !(fromUtc <= toUtc && (_Years.Value.StartTime > toUtc || _Years.Value.EndTime > fromUtc))
            || !_Years.HasValue)
        {
            var ts = _CronExpression.GetOccurrences(fromUtc, toUtc, fromInclusive, toInclusive);
            return ts.Select(r => r.AsKind(_DateTimeKind)).WhereIf(_Years.HasValue, r => r < _Years?.EndTime).Select(r => r.ChangeKind(from.Kind));
        }
        return Enumerable.Empty<DateTime>();
    }

    public IEnumerable<DateTime> GetOccurrences(DateTime from, DateTime to, TimeZoneInfo zone, bool fromInclusive = true, bool toInclusive = false)
    {
        var fromUtc = ConvertToUTC(from);
        var toUtc = ConvertToUTC(to);
        if (fromUtc > toUtc) return Enumerable.Empty<DateTime>();
        if (_Years.HasValue && !(fromUtc <= toUtc && (_Years.Value.StartTime > toUtc || _Years.Value.EndTime > fromUtc))
            || !_Years.HasValue)
        {
            var ts = _CronExpression.GetOccurrences(fromUtc, toUtc, zone, fromInclusive, toInclusive);
            return ts.Select(r => r.AsKind(_DateTimeKind)).WhereIf(_Years.HasValue, r => r < _Years?.EndTime).Select(r => r.ChangeKind(from.Kind));
        }
        return Enumerable.Empty<DateTime>();
    }

    public IEnumerable<DateTimeOffset> GetOccurrences(DateTimeOffset from, DateTimeOffset to, TimeZoneInfo zone, bool fromInclusive = true, bool toInclusive = false)
    {
        var fromUtc = from.UtcDateTime;
        var toUtc = to.UtcDateTime;
        if (fromUtc > toUtc) return Enumerable.Empty<DateTimeOffset>();
        if (_Years.HasValue && !(fromUtc <= toUtc && (_Years.Value.StartTime > toUtc || _Years.Value.EndTime > fromUtc))
            || !_Years.HasValue)
        {
            var ts = _CronExpression.GetOccurrences(from, to, zone, fromInclusive, toInclusive);
            return ts.WhereIf(_Years.HasValue, r => r.UtcDateTime.AsKind(_DateTimeKind) < _Years?.EndTime);
        }
        return Enumerable.Empty<DateTimeOffset>();
    }
    public override string ToString()
    {
        return _Expression;
    }

    public static bool operator ==(Cron left, Cron right)
    {
        return left._Expression == right._Expression && left._DateTimeKind == right._DateTimeKind;
    }

    public static bool operator !=(Cron left, Cron right)
    {
        return left._Expression != right._Expression || left._DateTimeKind != right._DateTimeKind;
    }
}
